/*
Copyright 2008 Ilya Novoselov

This file is part of TyuChat.

TyuChat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

TyuChat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with TyuChat.  If not, see <http://www.gnu.org/licenses/>.
*/

function submitInput(target, input)
{
    url = "/submitInput";
    
    var inputSent = function(data) {
    }
  
    var error = function (err) {
        alert("Couldn't send input");
    };
    
    var req = {
        target: target,
        input: input,
        ticket: $('ticket').value
    };
    
    var d = doSimpleXMLHttpRequest(url, req);
    d.addCallbacks(inputSent, error);
    
    return false;
};

// format timestamp
function formatTimestamp(timestamp)
{
    var f = numberFormatter("00");
    
    var h = timestamp.getHours();
    var m = timestamp.getMinutes();
    var s = timestamp.getSeconds();
    
    return f(h) + ":" + f(m) + ":" + f(s);
};

// Stores last received event id.
var lastEventId = null;

// Panel that is active atm.
Panel.activePanel = null;
// All panels.
Panel.panels = {};
// Initializer
Panel.prototype.initialize = function (tabLabel) {
    this.tabLabel = tabLabel;

    // Switcher tab.
    this.tabElement = LI(null, tabLabel);
    connect(this.tabElement, 'onclick', this, 'activate');
    // Attach to DOM.
    appendChildNodes('tabs', this.tabElement);
    
    // These need to be attached by child classes.
    // Messages element.
    this.messagesElement = DIV({'class': 'messages'});
    // Input box element.
    this.inputBoxElement = INPUT({type: "text"});
    
    connect(this.inputBoxElement, 'onkeydown', this, 'onInputKeyDown');
    
    Panel.panels[tabLabel] = this;
};
// Active panel. 
Panel.prototype.activate = function () {
    if(Panel.activePanel && Panel.activePanel != this) {
        Panel.activePanel.hide();
        this.show();
    };
    
    if(!Panel.activePanel) {
        this.show();
    };

    this.inputBoxElement.focus();
    Panel.activePanel = this;
};
// Hide panel.
Panel.prototype.hide = function () {
    this.tabElement.className = null;
    hideElement(this.element);
};
// Show panel.
Panel.prototype.show = function () {
    this.tabElement.className = 'active';
    showElement(this.element);
    // Scroll to bottom.
    this.scrollDown();
};
Panel.prototype.getScrollPosition = function () {
    var mf = this.messagesFrame;
    return mf.scrollHeight - mf.clientHeight - mf.scrollTop;
};
Panel.prototype.scrollDown = function () {
    var mf = this.messagesFrame;
    mf.scrollTop = mf.scrollHeight - mf.clientHeight;
};
// Print message.
Panel.prototype.printMessage = function (timestamp, msg) {
    // If window is scrolled to the bottom, scroll it further.
    var wantScroll = false;
    if(this.getScrollPosition() == 0)
        wantScroll = true;
    
    message = ["[", formatTimestamp(timestamp), "] ", msg];
    appendChildNodes(this.messagesElement, message, BR());
    
    if(wantScroll)
        this.scrollDown();
};
// Static method for notices.
Panel.printNotice = function (timestamp, msg) {
    Panel.activePanel.printMessage(timestamp, msg);
};
// Static method for broadcast.
Panel.broadCast = function (timestamp, msg) {
    for(tabLabel in Panel.panels) {
        var panel = Panel.panels[tabLabel];
        panel.printMessage(timestamp, msg);
    };
};
// Detect enter press.
Panel.prototype.onInputKeyDown = function (event) {
    if(event.key().string == 'KEY_ENTER') {
        // Submit input and clear input box.
        submitInput(this.tabLabel, this.inputBoxElement.value);
        this.inputBoxElement.value = "";
    };
};
Panel.addressNick = function (nickname) {
    inputBox = Panel.activePanel.inputBoxElement;
    inputBox.value = nickname + ', ' + inputBox.value;
    inputBox.focus();
};
// Constructor.
function Panel(tabLabel)
{
    if(tabLabel)
       this.initialize(tabLabel);
};

// TODO: this must be done somewhere in Panel.
/*          textFrame = $(activeTab + '_messages');
            needScroll = false;
            
            // Scroll only if window is at the bottom
            if(textFrame.scrollHeight - textFrame.scrollTop == textFrame.clientHeight)
                needScroll = true; */       
        // Scroll messages frame.
        /* if(needScroll)
            textFrame.scrollTop = textFrame.scrollHeight */

Peer.prototype = new Panel();
Peer.prototype.constructor = Peer;
Peer.peers = {};
// Initializer.
Peer.prototype.initialize = function (peerName) {
    Panel.prototype.initialize.call(this, peerName);
    
    this.messagesFrame = DIV({'class': "peer_text frame"}, this.messagesElement);
    
    this.element = DIV(null,
        // Text frame.
        this.messagesFrame,
    
        // Input box.
        DIV({'class': "input frame"}, this.inputBoxElement));
    
    // Attach to DOM.
    appendChildNodes(document.body, this.element);
    
    Peer.peers[peerName] = this;
};
// Find peer.
Peer.get = function (peerName) {
    return Peer.peers[peerName];
};
// Constructor.
function Peer(peerName)
{
    if(peerName) {
       this.initialize(peerName);
       this.activate();
    };
};

// Channel object
Channel.prototype = new Panel();
Channel.prototype.constructor = Channel;
Channel.channels = {};

Channel.prototype.initialize = function(channelName) {
    Panel.prototype.initialize.call(this, channelName);

    // Userlist stats element.
    this.statsElement = DIV({'class': "userlist_stats"});
    // Userlist element.
    this.userlistElement = TBODY();

    // Topic element.
    this.topicElement = INPUT({type: "text"});
    
    this.messagesFrame = DIV({'class': "text frame"}, this.messagesElement);
   
    // Channel form.
    this.element = DIV(null,
        // Userlist stats frame.
        this.statsElement,
        
        // Userlist frame
        DIV({'class': "userlist_frame frame"},
            TABLE(null, this.userlistElement)),
        
        // Text frame.
        this.messagesFrame,
        
        // Topic frame.
        DIV({'class': "topic frame"}, this.topicElement),

        // Input box.
        DIV({'class': "input frame"}, this.inputBoxElement));
        
    // Userlist.
    this.users = [];
    
    appendChildNodes(document.body, this.element);
    
    Channel.channels[channelName] = this;
};
// Static method for getting channel.
Channel.get = function (channelName) {
    return Channel.channels[channelName];
};
// Find channel for user.
Channel.findUser = function (nickname) {    
    return rv = filter(function (channel) {
        return some(channel.users, function(user) {
            return user.nickname == nickname;
        })
    }, values(Channel.channels));
};
Channel.prototype.addUser = function (mode, nickname, dontRedraw) {
    var user = {
        mode: mode,
        nickname: nickname
    };
    
    // FIXME: too slow on big channels.
    this.users.push(user);
    
    this.users.sort(function(a, b) {
        var rank_a = - "+%@&~".indexOf(a.mode);
        var rank_b = - "+%@&~".indexOf(b.mode);
        return compare([rank_a, a.nickname.toLowerCase()],
            [rank_b, b.nickname.toLowerCase()])
    });
    
    if(!dontRedraw) {
        this.redrawUserlist();
    }
};
Channel.prototype.removeUser = function (nickname) {
    for(var i = 0; i < this.users.length; i++) {
        if(this.users[i].nickname == nickname) {
           this.users.splice(i, 1);
           break;
        }
    }
    
    this.redrawUserlist();
}
Channel.prototype.renameUser = function (oldname, newname) {
    for(var i = 0; i < this.users.length; i++) {
        if(this.users[i].nickname == oldname) {
           this.users[i].nickname = newname;
           break;
        }
    }
    
    this.redrawUserlist();
}
// Redraws userlist.
Channel.prototype.redrawUserlist = function () {     
    var ops = 0;
    var total = this.users.length;
    
    var nodes = map(function(user) {        
        var mode = TD(null, user.mode);
        var nickname = TD(null, user.nickname);
        
        if(user.mode == '@') {
            ops += 1;
        };
        
        clickable = SPAN({"class": "clickable"}, nickname);
        connect(clickable, 'onclick', partial(Panel.addressNick, user.nickname));
        
        return TR(null, mode, clickable);
    }, this.users);
    
    replaceChildNodes(this.userlistElement, nodes);
    this.statsElement.textContent = ops + " ops, " + total + " total";
};
Channel.prototype.setTopic = function (topic) {
    this.topicElement.value = topic;
};
function Channel(channelName)
{
    if(channelName) {
        this.initialize(channelName);
        this.activate();
    };
};

function handleEvent(event) {
    var timestamp = isoTimestamp(event.timestamp);
    
    var isPeerMessage = false;
    
    if(event.channel == $('nickname').value) {
        isPeerMessage = true;
    };

    switch(event.type) {
        case 'names':
            var channel = Channel.get(event.channel);
            forEach(event.names, function(name) {
                channel.addUser(name.mode, name.nickname, true);
            });
            channel.redrawUserlist();
            break;
        case 'privmsg':
        case 'action':
            var nickname = event.user.split("!", 1);
            
            clickable = SPAN({"class": "clickable"}, nickname);
            connect(clickable, 'onclick', partial(Panel.addressNick, nickname));
            
            if(event.type == 'privmsg') {
                var message = ["<", clickable, "> ", event.msg];
            } else {
                var message = ["* ", clickable, " ", event.data];
            }

            if(isPeerMessage) {
                // Open peer tab, if not exists.
                var peer = Peer.get(nickname);
                if(!peer) {
                    peer = new Peer(nickname);
                }
                peer.printMessage(timestamp, message);
            } else {
                // Maybe this is channel message.
                panel = Channel.get(event.channel);
                // But maybe this is peer window.
                if(!panel)
                    panel = Peer.get(event.channel);
                panel.printMessage(timestamp, message);
            }
            break;
        case 'notice':
            var nickname = event.user.split("!", 1);
            var message = ["-", nickname, "- ", event.msg];
            Panel.printNotice(timestamp, message);
            break;
        case 'join':
            var channel = Channel.get(event.channel);
            channel.addUser(null, event.user);
            var message =  ["--> ", event.user, " has joined ", event.channel];
            channel.printMessage(timestamp, message);
            break;
        case 'kick':
            var channel = Channel.get(event.channel);
            channel.removeUser(event.kickee, true);            
            var message = ["<-- ", event.kicker, " was kicked by ", event.kickee
                , " (", event.message, ")"];
            channel.printMessage(timestamp, message);
            break;
        case 'leave':
            var channel = Channel.get(event.channel);
            // Delete user from channel userlist.
            channel.removeUser(event.user);
            var message = ["<-- ", event.user, " left ", event.channel];
            channel.printMessage(timestamp, message);
            break;
        case 'quit':
            var nickname = event.user;
            var message = ["<-- ", event.user, " has quit ("
               , event.quitMessage, ")"];
            // Report event on all channels user were.
            forEach(Channel.findUser(nickname), function (channel) {
                // Delete user from that channel userlist.
                channel.removeUser(nickname);
                // Report event.
                channel.printMessage(timestamp, message);
            });
            // Report in user's inlay, if it is open.
            var peer = Peer.get(nickname);
            if(peer)
                peer.printMessage(timestamp, message);
            break;
        case 'nick':
            var message = ["*** ", event.oldname, " is now know as "
               , event.newname];
            // Report event on all channels user is.
            forEach(Channel.findUser(nickname), function (channel) {
                // Rename user.
                channel.renameUser(event.oldname, event.newname);
                // Report event.
                channel.printMessage(timestamp, message);
            });
            var peer = Peer.get(nickname);
            if(peer)
                peer.printMessage(timestamp, message);    
            break;
        case 'signedOn':
            var message = "*** Connection established"
            Panel.printNotice(timestamp, message);
            break;
        case 'joined':
            var message = ["*** Now talking on ", event.channel];
            var channel = new Channel(event.channel);
            channel.printMessage(timestamp, message);
            break;
        case 'nickChanged':
            // Woohoo, our nickname changed!
            var nickname = event.nick;
            $('nickname').value = nickname;
            var message = "*** You are now known as ", nickname;
            Panel.broadCast(timestamp, message);
            break;
        case 'connectionLost':
            var message = ["*** Connection lost (", event.reason, ")"];
            Panel.broadCast(timestamp, message);
            break;
        case 'disconnect':
            var message = "*** Disconnected";
            Panel.broadCast(timestamp, message);
            break;
        case 'error':
            var message = ["*** ", event.message];
            Panel.printNotice(timestamp, message);
            break;
        default:
            Panel.printNotice(timestamp, "*** Unknown event type (", event.type, ")")
    };
};

// fetch new events and put them into window
function fetchEvents()
{
    var url = "/getEvents";
   
    var gotEvents = function (events) {
        // Display events.
        forEach(events, function(event) {
            lastEventId = event.id;
            
            try {
                handleEvent(event);
            } catch(e) {
                logFatal(e);
            };
        });
        
        // Start request again.
        fetchEvents();
    };
    
    var error = function (err) {
        if(err instanceof XMLHttpRequestError & err.number == 404) {
            errorMessage("FATAL: Ticket not found");
        } else {
            var errorDelay = 15;
            errorMessage("Error trying to fetch events (", repr(err), "), retry in ", errorDelay, " seconds");
            callLater(errorDelay, fetchEvents);
        }
    };
    
    // Get ticket.
    var ticket = $('ticket').value;
    
    var d = loadJSONDoc(url, {lastId: lastEventId, ticket: ticket});
    d.addCallbacks(gotEvents, error);
};

function start()
{
    var server = $('server').value;
    
    // Server tab.
    var peer = new Peer(server);
    
    // start fetchEvents sequence
    fetchEvents();
}

// addLoadEvent(testPanels);
// addLoadEvent(testEvents);
addLoadEvent(start);